﻿// 2023/3/8: 首个版本
// 2023/8/28: 环境包点击运行后3秒不可再点
// 2023/9/25: 改为直接输入Icon对象
// 2023/11/15: 修正点击安装后按钮未禁用问题
// 2024/1/19: 默认情况使用PlatformFunctionDefault
// 2024/5/17: 支持根据已安装和已启用插件进行筛选
// 2024/6/25: 支持API3，支持异步。支持显示异步不支持错误
// 2024/10/18: 使用新的安装插件回调接口

using System;
using System.Collections.Generic;
using System.IO;
using System.Diagnostics;
using System.Threading;
using ASEva;
using ASEva.Utility;
using ASEva.Samples;
using ASEva.UIEto;
using Eto.Drawing;
using Eto.Forms;

namespace CommonSource
{
    partial class PluginInstaller : DialogPanel
    {
        public PluginInstaller(InstallPluginLibraryInfo[] libs, InstallPluginDriverInfo[] drivers, WorkflowInstallPluginCallback callback, Icon icon = null)
        {
            this.libs = libs;
            this.libsNewInstalled = new bool[libs.Length];
            this.drivers = drivers;
            this.callback = callback;

            Icon = icon;
            Title = t["title"];
            SetFixMode(400, 600, true);

            var mainLayout = this.SetContentAsColumnLayout();
            var libraryLayout = mainLayout.AddGroupBox(t["library"], true).SetContentAsColumnLayout();
            driverLayout = mainLayout.AddGroupBox(t["environment-driver"], false, 0, 150).SetContentAsColumnLayout();

            var listLibrary = libraryLayout.AddControl(new CheckableListBox(), true) as CheckableListBox;
            var libChecksLayout = libraryLayout.AddRowLayout();
            var checkOnlyInstalled = libChecksLayout.AddCheckBox(t["filter-installed"]);
            var checkOnlyEnabled = libChecksLayout.AddCheckBox(t["filter-enabled"]);
            checkOnlyEnabled.Enabled = false;
            var libButtonsLayout = libraryLayout.AddRowLayout();
            var buttonSelectAll = libButtonsLayout.AddButton(t["select-all"], false, 100);
            var buttonClearAll = libButtonsLayout.AddButton(t["clear-all"], false, 100);
            libButtonsLayout.AddSpace();
            var buttonInstall = libButtonsLayout.AddButton(t["install"], false, 80);
            buttonInstall.Enabled = false;

            var listDriver = driverLayout.AddControl(new ListBox(), true) as ListBox;
            var driverButtonLayout = driverLayout.AddRowLayout();
            driverButtonLayout.AddSpace();
            var buttonRun = driverButtonLayout.AddButton(t["run"], false, 80);
            buttonRun.Enabled = false;

            var itemsText = new List<String>();
            var itemsEnable = new List<bool>();
            var itemsCheck = new List<bool>();
            foreach (var lib in libs)
            {
                String displayText = null;
                bool defaultUncheck = false;
                if (lib.Status == InstallPluginLibraryStatus.GUIUnsupported)
                {
                    displayText = lib.Name + t["gui-unsupported"];
                }
                else if (lib.Status == InstallPluginLibraryStatus.AsyncUnsupported)
                {
                    displayText = lib.Name + t["async-unsupported"];
                }
                else if (lib.Status == InstallPluginLibraryStatus.TooNew)
                {
                    displayText = lib.Name + t["too-new"];
                }
                else if (lib.Status == InstallPluginLibraryStatus.TooOld)
                {
                    displayText = lib.Name + t["too-old"];;
                }
                else if (lib.InstalledVersion != null)
                {
                    var curPackVersionText = lib.InstalledVersion == null ? t["unknown-version"] : lib.InstalledVersion.ToString();
                    var targetPackVersionText = lib.Version == null ? t["unknown-version"] : lib.Version.ToString();

                    if (lib.InstalledVersion != null && lib.Version != null && curPackVersionText == targetPackVersionText)
                    {
                        displayText = lib.Name + t.Format("reinstall", curPackVersionText);
                        defaultUncheck = true;
                    }
                    else
                    {
                        displayText = lib.Name + " (" + curPackVersionText + " >> " + targetPackVersionText + ")";
                        defaultUncheck = lib.InstalledVersion != null && lib.Version != null && lib.Version < lib.InstalledVersion;
                    }
                }
                else
                {
                    var targetPackVersionText = lib.Version == null ? t["unknown-version"] : lib.Version.ToString();
                    displayText = lib.Name + " (" + targetPackVersionText + ")";
                }

                itemsText.Add(displayText);
                itemsEnable.Add(lib.Status == InstallPluginLibraryStatus.OK);
                itemsCheck.Add(lib.Status == InstallPluginLibraryStatus.OK && !defaultUncheck);
            }
            listLibrary.AddItems(itemsText.ToArray(), itemsCheck.ToArray(), itemsEnable.ToArray());

            foreach (var driver in drivers)
            {
                String text = null;
                if (!driver.Attached)
                {
                    text = driver.Name + t["not-attached"];
                }
                else
                {
                    text = driver.Name;
                }

                listDriver.Items.Add(text);
            }

            switch (App.GetRunningOS())
            {
                case "windows":
                case "macos":
                    platform = new PlatformFunctionDefault();
                    break;
                case "linux":
                case "linuxarm":
                    platform = new PlatformFunctionLinux(driverLayout);
                    break;
            }

            checkOnlyInstalled.CheckedChanged += delegate
            {
                if (checkOnlyInstalled.Checked.Value) checkOnlyEnabled.Enabled = true;
                else checkOnlyEnabled.Checked = checkOnlyEnabled.Enabled = false;

                for (int i = 0; i < libs.Length; i++)
                {
                    if (libsNewInstalled[i]) continue;

                    var checkShouldEnable = true;
                    if (checkOnlyInstalled.Checked.Value) checkShouldEnable = libs[i].InstalledStatus != PluginInstalledStatus.NotInstalled;

                    listLibrary.SetEnabled(i, checkShouldEnable);
                    if (!checkShouldEnable && listLibrary.GetChecked(i)) listLibrary.SetChecked(i, false); 
                }
            };

            checkOnlyEnabled.CheckedChanged += delegate
            {
                for (int i = 0; i < libs.Length; i++)
                {
                    if (libsNewInstalled[i]) continue;

                    var checkShouldEnable = true;
                    if (checkOnlyEnabled.Checked.Value) checkShouldEnable = libs[i].InstalledStatus == PluginInstalledStatus.InstalledAndEnabled;
                    else if (checkOnlyInstalled.Checked.Value) checkShouldEnable = libs[i].InstalledStatus != PluginInstalledStatus.NotInstalled;

                    listLibrary.SetEnabled(i, checkShouldEnable);
                    if (!checkShouldEnable && listLibrary.GetChecked(i)) listLibrary.SetChecked(i, false); 
                }
            };

            buttonClearAll.Click += delegate
            {
                listLibrary.UncheckAll();
            };
            
            buttonSelectAll.Click += delegate
            {
                listLibrary.CheckAll();
            };

            buttonInstall.Click += async delegate
            {
                var errorMsgs = new List<String>();
                for (int i = 0; i < libs.Length; i++)
                {
                    if (!listLibrary.GetChecked(i)) continue;

                    var errorMsg = callback.InstallLibrary(libs[i].LibraryID);
                    if (errorMsg != null) errorMsgs.Add(errorMsg);

                    listLibrary.SetText(i, libs[i].Name + t["installed"]);
                    listLibrary.SetChecked(i, true);
                    listLibrary.SetEnabled(i, false);
                    libsNewInstalled[i] = true;
                }

                foreach (var errorMsg in errorMsgs)
                {
                    await AgencyLocal.PopupError(errorMsg);
                }
            };

            buttonRun.Click += async delegate
            {
                var driver = drivers[listDriver.SelectedIndex];
                var prepareResult = callback.PrepareDriver(driver.DriverID);
                var exePath = prepareResult.Item1;
                var errorMsg = prepareResult.Item2;
                if (errorMsg == null)
                {
                    if (platform != null) platform.InstallEnvDriver(exePath, driver.Name);
                    buttonRun.Enabled = false;
                    runInstallerTime = DateTime.Now;
                }
                else
                {
                    await AgencyLocal.PopupError(errorMsg);
                }
            };

            timer = new UITimer();
            timer.Interval = 0.01;
            timer.Elapsed += delegate
            {
                bool withCheckedEnabled = false;
                foreach (var index in listLibrary.GetCheckedIndices())
                {
                    if (listLibrary.GetEnabled(index))
                    {
                        withCheckedEnabled = true;
                        break;
                    }
                }
                buttonInstall.Enabled = withCheckedEnabled;

                bool startingInstaller = false;
                if (runInstallerTime != null)
                {
                    if ((DateTime.Now - runInstallerTime.Value).TotalSeconds > 3) runInstallerTime = null;
                    else startingInstaller = true;
                }
                buttonRun.Enabled = !startingInstaller && listDriver.SelectedIndex >= 0 && drivers[listDriver.SelectedIndex].Attached;

                if (platform != null) platform.OtherLoopOperations();
            };
            timer.Start();
        }

        public override void OnClosing()
        {
            timer.Stop();
        }

        private interface PlatformFunction
        {
            void InstallEnvDriver(String exePath, String driverName);
            void OtherLoopOperations();
        }

        private class PlatformFunctionDefault : PlatformFunction
        {
            public void InstallEnvDriver(String exePath, String driverName)
            {
                AgencyLocal.StartProcess(exePath);
            }

            public void OtherLoopOperations()
            { 
            }
        }

        private class PlatformFunctionLinux : PlatformFunction
        {
            public PlatformFunctionLinux(StackLayout driverLayout)
            {
                this.driverLayout = driverLayout;
            }

            public void InstallEnvDriver(String exePath, String driverName)
            {
                if (exePath != null && exePath.EndsWith(".sh"))
                {
                    runner = new ScriptRunner(exePath);
                    driverLayout.Enabled = false;
                }
                else
                {
                    AgencyLocal.StartProcess(exePath);
                }
            }

            public void OtherLoopOperations()
            { 
                if (runner != null && runner.IsOver())
                {
                    driverLayout.Enabled = true;
                    runner = null;
                }
            }

            private ScriptRunner runner = null;
            private StackLayout driverLayout;
        }

        private class ScriptRunner
        {
            public ScriptRunner(String targetShFile) // 绝对路径
            {
                target = targetShFile;
                thread = new Thread(workFunc);
                thread.Start();
            }

            private void workFunc(object obj)
            {
                var startInfo = new ProcessStartInfo();
                startInfo.FileName = "gnome-terminal";
                startInfo.Arguments = "--wait -- bash \"" + target + "\"";
                startInfo.WorkingDirectory = Path.GetDirectoryName(target);

                var process = Process.Start(startInfo);

                while (true)
                {
                    Thread.Sleep(100);
                    if (process.WaitForExit(1)) break;
                }

                isOver = true;
            }

            public bool IsOver()
            {
                return isOver;
            }

            private bool isOver = false;
            private Thread thread = null;
            private String target;
        }

        private UITimer timer;

        private InstallPluginLibraryInfo[] libs;
        private bool[] libsNewInstalled;
        private InstallPluginDriverInfo[] drivers;
        private WorkflowInstallPluginCallback callback;

        private TextResource t = TextResource.Load(Convert.FromBase64String("PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHRleHRfcmVzb3VyY2U+CiAgPHQgaWQ9InRpdGxlIj4KICAgIDxlbj5QbHVnaW4gaW5zdGFsbGVyPC9lbj4KICAgIDx6aD7mj5Lku7blronoo4Xlmag8L3poPgogIDwvdD4KICA8dCBpZD0ibGlicmFyeSI+CiAgICA8ZW4+TGlicmFyeTwvZW4+CiAgICA8emg+5bqT5paH5Lu2PC96aD4KICA8L3Q+CiAgPHQgaWQ9InNlbGVjdC1hbGwiPgogICAgPGVuPlNlbGVjdCBhbGw8L2VuPgogICAgPHpoPuWFqOmAiTwvemg+CiAgPC90PgogIDx0IGlkPSJjbGVhci1hbGwiPgogICAgPGVuPkNsZWFyIGFsbDwvZW4+CiAgICA8emg+5riF56m6PC96aD4KICA8L3Q+CiAgPHQgaWQ9Imluc3RhbGwiPgogICAgPGVuPkluc3RhbGw8L2VuPgogICAgPHpoPuWuieijhTwvemg+CiAgPC90PgogIDx0IGlkPSJlbnZpcm9ubWVudC1kcml2ZXIiPgogICAgPGVuPkVudmlyb25tZW50IC8gRHJpdmVyPC9lbj4KICAgIDx6aD7njq/looMv6amx5YqoPC96aD4KICA8L3Q+CiAgPHQgaWQ9InJ1biI+CiAgICA8ZW4+UnVuPC9lbj4KICAgIDx6aD7ov5DooYw8L3poPgogIDwvdD4KICA8dCBpZD0iZ3VpLXVuc3VwcG9ydGVkIj4KICAgIDxlbj4gKEdVSSB1bnN1cHBvcnRlZCk8L2VuPgogICAgPHpoPiAo55WM6Z2i5qGG5p625LiN5pSv5oyBKTwvemg+CiAgPC90PgogIDx0IGlkPSJhc3luYy11bnN1cHBvcnRlZCI+CiAgICA8ZW4+IChBc3luYyB1bnN1cHBvcnRlZCk8L2VuPgogICAgPHpoPiAo5byC5q2l5LiN5pSv5oyBKTwvemg+CiAgPC90PgogIDx0IGlkPSJ0b28tbmV3Ij4KICAgIDxlbj4gKFRvbyBuZXcpPC9lbj4KICAgIDx6aD4gKOeJiOacrOWkquaWsCk8L3poPgogIDwvdD4KICA8dCBpZD0idG9vLW9sZCI+CiAgICA8ZW4+IChUb28gb2xkKTwvZW4+CiAgICA8emg+ICjniYjmnKzlpKrml6cpPC96aD4KICA8L3Q+CiAgPHQgaWQ9InVua25vd24tdmVyc2lvbiI+CiAgICA8ZW4+VW5rbm93bi12ZXJzaW9uPC9lbj4KICAgIDx6aD7mnKrnn6XniYjmnKw8L3poPgogIDwvdD4KICA8dCBpZD0ibm90LWF0dGFjaGVkIj4KICAgIDxlbj4gKE5vdCBhdHRhY2hlZCk8L2VuPgogICAgPHpoPiAo5pyq6ZqP5LuYKTwvemg+CiAgPC90PgogIDx0IGlkPSJpbnN0YWxsZWQiPgogICAgPGVuPiAoSW5zdGFsbGVkKTwvZW4+CiAgICA8emg+ICjlt7Llronoo4UpPC96aD4KICA8L3Q+CiAgPHQgaWQ9Imluc3RhbGxpbmciPgogICAgPGVuPkluc3RhbGxpbmcgPC9lbj4KICAgIDx6aD7mraPlnKjlronoo4U8L3poPgogIDwvdD4KICA8dCBpZD0iaW5zdGFsbC1vdmVyIj4KICAgIDxlbj5JbnN0YWxsYXRpb24gb3ZlcjwvZW4+CiAgICA8emg+5bey57uT5p2f5a6J6KOFPC96aD4KICA8L3Q+CiAgPHQgaWQ9InJlaW5zdGFsbCI+CiAgICA8ZW4+ICh7MH0gcmVpbnN0YWxsKTwvZW4+CiAgICA8emg+ICh7MH3ph43oo4UpPC96aD4KICA8L3Q+CiAgPHQgaWQ9ImZpbHRlci1pbnN0YWxsZWQiPgogICAgPGVuPk9ubHkgaW5zdGFsbGVkPC9lbj4KICAgIDx6aD7ku4XpmZDlt7Llronoo4U8L3poPgogIDwvdD4KICA8dCBpZD0iZmlsdGVyLWVuYWJsZWQiPgogICAgPGVuPk9ubHkgZW5hYmxlZDwvZW4+CiAgICA8emg+5LuF6ZmQ5bey5ZCv55SoPC96aD4KICA8L3Q+CjwvdGV4dF9yZXNvdXJjZT4="), AgencyLocal.GetAppLanguage());

        private StackLayout driverLayout;

        private PlatformFunction platform;

        private DateTime? runInstallerTime = null;
    }
}